﻿/****************************************************************
  Author:  Ievgenii Nazaruk, ievgenii.nazaruk@gmail.com 
 License:  Microsoft Public License (Ms-PL)
          ( http://www.opensource.org/licenses/ms-pl.html or
            http://www.microsoft.com/opensource/licenses.mspx )
*****************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TableParsers;
using System.Runtime.Serialization;
using System.ComponentModel;
using SpeakupBackend.Themes;
using WebLayer.TableParsers;
using WebLayer;

namespace SpeakupBackend.Classes
{
    public enum ClassAction 
    {
        None, 
        Reserve, 
        CancelReservation 
    };

    public class ClassStatusChangedArgs: EventArgs
    {
        public ClassStatus OldStatus {get; set;}
        public ClassStatus NewStatus {get; set;}
        public Class Source {get; set;}
    }
    
    [Serializable]
    public partial class Class : INotifyPropertyChanged
    {
        public Class(int classId, ClassName name, ClassTime time, int unit, ClassStatus status, ClassAction action)
        {
            if (name == null)
            {
                throw new NullReferenceException("Name");
            }

            if (time == null)
            {
                throw new NullReferenceException("Time");
            }

            m_id = classId;
            m_name = name;
            m_time = time;
            m_unit = unit;
            m_currentStatus = status;
            m_action = action;
        }

        /// <summary>
        /// Class id - an internal id number of class that is used by speakup server.
        /// </summary>
        private int m_id = 0;
        public int Id
        {
            get 
            { 
                return m_id; 
            }
            internal set 
            {
                m_id = value;
                OnPropertyChanged("Id");
            }
        }

        /// <summary>
        /// Class name
        /// </summary>
        private ClassName m_name = null;
        public ClassName Name
        {
            get 
            { 
                return m_name; 
            }
            private set 
            { 
                m_name = value;
                OnPropertyChanged("Name");
            }
        }

        /// <summary>
        /// Class start/end time
        /// </summary>
        private ClassTime m_time = null;
        public ClassTime Time
        {
            get 
            { 
                return m_time;
            }
            internal set 
            { 
                m_time = value;
                OnPropertyChanged("Time");
            }
        }

        /// <summary>
        /// Class status.
        /// </summary>
        private ClassStatus m_currentStatus = ClassStatus.Unknown;
        public ClassStatus CurrentStatus
        {
            get 
            { 
                return m_currentStatus;  
            }
            private set 
            {
                if (value != m_currentStatus)
                {
                    ClassStatus oldStatus = m_currentStatus;
                    m_currentStatus = value;

                    OnClassStatusChanged(oldStatus, value);
                }
            }
        }

        private int m_unit = 0;
        public int Unit
        {
            get
            {
                return m_unit;
            }
            internal set
            {
                m_unit = value;
                OnPropertyChanged("Unit");
            }
        
        }

        public Level Level
        {
            get
            {
                if (!Level.IsValidUnitNumber(Unit))
                {
                    return null;
                }

                return Level.GetLevelByUnit(Unit);
            }
        }

        private ITheme m_theme;
        public ITheme Theme
        {
            get
            {
                return m_theme;
            }

            internal set
            {
                if (value != m_theme)
                {
                    m_theme = value;
                    OnPropertyChanged("WorkshopTheme");
                }
            }
        }

        public override bool Equals(object obj)
        {
            Class speakupClass = obj as Class;
            if (speakupClass == null)
            {
                throw new InvalidCastException();
            }

            if (speakupClass.Id != Id)
            {
                return false;
            }

            if (speakupClass.Time.Begin != Time.Begin)
            {
                return false;
            }

            if (speakupClass.Time.End != Time.End)
            {
                return false;
            }

            if (speakupClass.Time.Date != Time.Date)
            {
                return false;
            }

            if (speakupClass.Name.Kind != Name.Kind)
            {
                return false;
            }

            if (speakupClass.Name.Name != Name.Name)
            {
                return false;
            }

            return true;
        }
        public override int GetHashCode()
        {
            int hash = Id;
            unchecked
            {
                hash += 83 * Time.Begin.GetHashCode();
                hash += 83 * Time.End.GetHashCode();
                hash += 83 * Time.Date.GetHashCode();
                hash += 83 * Name.Kind.GetHashCode();
                hash += 83 * Name.Name.GetHashCode();
            }
            return hash;
        }

        public override string ToString()
        {
            return String.Format(@"{0} ({1}) {2} {3:hh\:mm} - {4:hh\:mm}", 
                Name.Name, Name.Type, 
                Time.Date.ToShortDateString(),
                Time.Begin, Time.End);
        }
        
        [field:NonSerialized]
        public event EventHandler<ClassStatusChangedArgs> ClassStatusChanged;
        protected virtual void OnClassStatusChanged(ClassStatus oldStatus, ClassStatus newStatus)
        {
            if (ClassStatusChanged != null)
            {
                ClassStatusChangedArgs args = new ClassStatusChangedArgs();
                args.Source = this;
                args.OldStatus = oldStatus;
                args.NewStatus = newStatus;

                ClassStatusChanged(this, args);
            }

            OnPropertyChanged("CurrentStatus");
        }

        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName);
            if (PropertyChanged != null)
            {
                PropertyChanged(this, args);
            }
        }
    }

    /// <summary>
    /// Action related functionality
    /// </summary>
    public partial class Class
    {
        private ClassAction m_action = ClassAction.None;
        public ClassAction Action
        {
            get
            {
                return m_action;
            }
            internal set
            {
                ClassStatus oldStatus = DesiredStatus;

                if (value == ClassAction.None)
                {
                    m_action = value;
                }
                else if (value == ClassAction.Reserve)
                {
                    //only available classes can be reserved
                    if (CurrentStatus == ClassStatus.Available ||
                        CurrentStatus == ClassStatus.AvailableWaitList)
                    {
                        m_action = ClassAction.Reserve;
                    }
                    else
                    {
                        throw new ArgumentException("Can not reserve class that has " + CurrentStatus + " status");
                    }
                }
                else if (value == ClassAction.CancelReservation)
                {
                    //only reserved classes can be cancelled
                    if (CurrentStatus.IsReserved())
                    {
                        m_action = ClassAction.CancelReservation;
                    }
                    else
                    {
                        throw new ArgumentException("Can not cancel reservation on class that has " + CurrentStatus + " status");
                    }
                }
                else
                {
                    throw new ArgumentException("Action");
                }

                ClassStatus newStatus = DesiredStatus;
                if (newStatus != oldStatus)
                {
                    OnDesiredStatusChanged(oldStatus, DesiredStatus);
                }

                OnPropertyChanged("Action");

            }
        }

        public ClassStatus DesiredStatus
        {
            get
            {
                if (Action == ClassAction.Reserve)
                {
                    if (CurrentStatus == Classes.ClassStatus.Available)
                    {
                        return Classes.ClassStatus.Reserved;
                    }
                    else if (CurrentStatus == Classes.ClassStatus.AvailableWaitList)
                    {
                        return Classes.ClassStatus.ReservedWaitList;
                    }
                }
                else if (Action == ClassAction.CancelReservation)
                {
                    if (CurrentStatus == Classes.ClassStatus.Reserved)
                    {
                        return Classes.ClassStatus.Available;
                    }
                    else if (CurrentStatus == Classes.ClassStatus.ReservedWaitList)
                    {
                        return Classes.ClassStatus.AvailableWaitList;
                    }
                }

                return CurrentStatus;
            }
        }

        /// <summary>
        /// Can this class be reserved?
        /// (Class status Available or AvailableWaitList or CancelReservation action was requested)
        /// </summary>
        public bool IsReservable
        {
            get
            {
                if (Action == ClassAction.None)
                {
                    return CurrentStatus == ClassStatus.Available ||
                           CurrentStatus == ClassStatus.AvailableWaitList;
                }

                return Action == ClassAction.CancelReservation;
            }
        }

        /// <summary>
        /// Is class cancellable?
        /// (Class status Resreved or ReservedWaitList or Reserve action was requested)
        /// </summary>
        public bool IsCancellable
        {
            get
            {
                if (Action == ClassAction.None)
                {
                    return CurrentStatus.IsReserved();
                }

                return Action == ClassAction.Reserve;
            }
        }

        /// <summary>
        /// Tries to reserve class if class is available for reservation.        
        /// If reservation is invalid for current class status (f.e. class is alread been attended)
        /// then an ArgumentException is thrown.
        /// </summary>
        public void Reserve()
        {
            if (Action == ClassAction.CancelReservation)
            {
                Action = ClassAction.None;
            }
            else
            {
                Action = ClassAction.Reserve;
            }
        }

        /// <summary>
        /// Tries to cancel reservation of reserved class.
        /// If class is available this function does nothing.
        /// If this operation is invalid for current class status (f.e. class is alread been attended)
        /// then an ArgumentException is thrown.
        /// </summary>
        public void CancelReservation()
        {
            if (Action == ClassAction.Reserve)
            {
                Action = ClassAction.None;
            }
            else
            {
                Action = ClassAction.CancelReservation;
            }
        }

        public bool IsActionPending
        {
            get
            {
                return Action != ClassAction.None;
            }
        }

        public void DiscardAction()
        {
            Action = ClassAction.None;
        }

        /// <summary>
        /// returns 
        /// </summary>
        public bool IsActionApplicable()
        {
            string message;
            return IsActionApplicable(out message);
        }

        public bool IsActionApplicable(out string description)
        {
            if (Action == ClassAction.None)
            {
                description = "No action is pending.";
                return true;
            }

            if (Id == 0)
            {
                description = "Can not apply actions on classes with Id == 0.";
                return false;
            }

            description = "Action can be applied.";
            return true;
        }

        public void ApplyAction(LoginToken token)
        {
            string message;
            ApplyAction(out message, token);
        }

        public void ApplyAction(out string serverMessage, LoginToken token)
        {
            string descr;
            if (!IsActionApplicable(out descr))
            {
                throw new InvalidOperationException(descr);
            }

            if (Action == ClassAction.None)
            {
                serverMessage = "Nothing was done.";
            }
            else if (Action == ClassAction.Reserve)
            {
                serverMessage = ClassActionRequest.ReserveClass(Id, token);
            }
            else if (Action == ClassAction.CancelReservation)
            {
                serverMessage = ClassActionRequest.CancelClass(Id, token);
            }
            else
            {
                throw new InvalidOperationException("Requested action is unrecognized");
            }
            
            //assume success
            this.CurrentStatus = DesiredStatus;
            this.Action = ClassAction.None;            
        }

        internal void RevertAction(ClassStatus currentStatus, ClassAction action)
        {
            this.CurrentStatus = currentStatus;
            this.Action = action;
        }

        [field: NonSerialized]
        public event EventHandler<ClassStatusChangedArgs> DesiredClassStatusChanged;
        protected virtual void OnDesiredStatusChanged(ClassStatus oldStatus, ClassStatus newStatus)
        {
            if (DesiredClassStatusChanged != null)
            {
                ClassStatusChangedArgs args = new ClassStatusChangedArgs();
                args.Source = this;
                args.OldStatus = oldStatus;
                args.NewStatus = newStatus;

                DesiredClassStatusChanged(this, args);
            }

            OnPropertyChanged("DesiredStatus");
        }
    }
}
